home *** CD-ROM | disk | FTP | other *** search
/ Magnum One / Magnum One (Mid-American Digital) (Disc Manufacturing).iso / d1 / bastips2.arc / BASICAL.TXT < prev    next >
Text File  |  1988-11-29  |  27KB  |  612 lines

  1.              The BASIC/Assembly Language Connection
  2.         (Personal Computer Age March 1984 by Dan Rollins)
  3.  
  4.      With all its variables, arrays, strings, control structure
  5. and flexible graphics and I/O commands, BASIC is a very convenient
  6. programming language.  But BASIC programs tend to execute slowly.
  7. Assembly language is very fast, but it is not convenient to use, nor
  8. is it easy to write.  Fortunately, we don't necessarily need to choose
  9. between the two.  We can write hybrid programs that are mostly written
  10. in BASIC, but which use machine language subroutines to speed up the
  11. time-critical parts of an application.
  12.      The commands PEEK, POKE, BLOAD, CLEAR, CALL, and USR are provided
  13. in the BASIC language for just that purpose.  There is an entire
  14. appendix in the BASIC manual describing how to make the connection,
  15. but a few short examples will go a long way toward explaining these
  16. concepts.
  17.      The first step is to decide which part of your program is to be
  18. coded in assembly language.  Is there any function essential to your
  19. program that BASIC doesn't supply?  You might consider writing a
  20. routine that upshifts (forces into uppercase) all the characters in
  21. a string.  Or perhaps you need to emulate the Applesoft calls that
  22. "clear to end of line" and "clear to end of screen."  The ROM BIOS
  23. contains more powerful "window" management routines but BASIC has no
  24. commands to make use of them.  BASIC supplies no way to change the
  25. default drive, but that can be accomplished by invoking a DOS service
  26. -- in all of 10 bytes of machine code.
  27.      Thus, you can use machine language to add new functions to BASIC.
  28. But the most important use of machine language subroutines is to
  29. increase the speed of a program.  A single CALL command can sort an
  30. array of strings at least 100 times faster than a corresponding BASIC
  31. subroutine.  Many BASIC programs do a lot or printing on the screen.
  32. If you have compared the speed of the PC's PRINT command to that of
  33. other personal computers, you know that this is one of the main
  34. bottlenecks in many programs.  For instance, many applications programs
  35. display a "form" for the user to fill in.  Normally the form must be
  36. constructed with dozens of LOCATE and PRINT commands.  This may take
  37. so much time that by the end of the day, the operator has lost an
  38. accumulated total of 30 minutes just waiting for the computer.
  39.      This slack time can be minimized with a short assembly language
  40. routine which duplicates some functions the PRINT command.  QPRINT.ASM,
  41. when assembled into QPRINT.COM, is a program that displays characters
  42. 10 times as quickly as the BASIC PRINT command.
  43.      The secret of QPRINT is that it bypasses most of the ROM BIOS
  44. overhead by throwing characters directly onto the screen.  The PC
  45. screen is a "memory-mapped" device.  To display a character on the
  46. screen, we need only to place the ASCII value of that character
  47. somewhere in a particular block of memory.  This block of "video
  48. memory" begins at one of two different places, depending upon which
  49. display adapter is being used.  For the Monochrome Adapter, video
  50. memory begins at segment B000H.  Color Graphics Adapter video memory
  51. begins at the B800H segment.
  52.      To keep this discussion centered around the machine language
  53. interface, we won't go into all of the details of multiple pages,
  54. graphics modes, etc.  For now, just use this rote formula to determine
  55. where to store a byte to display a character on the screen:
  56.  
  57. addr = (row-1) * 160 + (column-1) * 2
  58.  
  59. where ROW and COLUMN are values that are normally used in a LOCATE
  60. statement.  The following simple BASIC program will get you started
  61. on how video memory works.  It is actually a BASIC implementation of
  62. the QPRINT procedure:
  63.  
  64. 10 VID.SEG=&HB000      'For CGA, use &HB800
  65. 20 INPUT "Enter string, row, column :",A$,ROW,CLM
  66. 30 ADDR=(ROW-1)*160+(CLM-1)*2
  67. 40 DEF SEG=VID.SEG
  68. 50 FOR J=1 TO LEN(A$)
  69. 60 POKE ADDR,ASC(MID$(A$,J<1))
  70. 70 ADDR=ADDR+2
  71. 80 NEXT
  72. 90 GOTO 20
  73.  
  74.      This program just takes each character of the string and places
  75. it into video memory at the desired location.  In line 70, the ADDR
  76. variable is incremented twice, skipping the address of the display
  77. attribute, a byte which controls the color, blink, bold, and underline
  78. attribute of the character.
  79.      The first part of QPRINT reads the arguments passed from BASIC.
  80. After obtaining the screen coordinates, it calculates an address using
  81. an assembly language version of the formula presented above.  Then it
  82. determines the desired video segment by invoking the ROM BIOS EQUIPMENT
  83. CHECK service and deciphering the returned code.  Finally, the program
  84. uses the REP MOVSB command to copy the characters of the string
  85. variable directly to the video memory.
  86.      To make the codes simple, several things have been omitted which
  87. you might want to incorporate in an enhanced version.  For one thing,
  88. QPRINT assumes that the video card is in 80-column text mode.  If you
  89. have a color card and have executed a SCREEN 1 or SCREEN 2 command,
  90. QPRINT will not function as expected.  Also, color card users will
  91. soon become aware of another omission; as the characters are displayed,
  92. the screen will be disturbed by "video snow" -- flecks of white
  93. scattered around the screen.  This is relatively easy to avoid, but
  94. it's a subject for later consideration.
  95.      You might also want your enhanced version to do something about
  96. the display attribute.  As written, QPRINT simply ignores the
  97. attribute byte -- each character displayed takes on the color, blink,
  98. etc. of whatever character was previously at the position.  By tacking
  99. another parameter to the CALL, you could pass a selected attribute
  100. byte to the routine.  Add two parameters and include even more code
  101. to the QPRINT logic, and you could pass both a foreground and a
  102. background color.
  103.      Note that the program is contained entirely in one segment,
  104. CODE_SEG, and the END pseudo-op specifies the start of the code as
  105. the starting address.  These are requirements of the EXE2BIN utility
  106. which is used in one step of converting the code to BLOAD format.
  107.      The comments at the top of QPRINT.ASM contain a BASIC program
  108. which uses QPRINT.  Before you enter these into a BASIC program, the
  109. listing must be assembled, linked, processed with the EXE2BIN utility,
  110. and processed with the BIN2BLD program below.  This sound like a lot
  111. of steps, but they can all be automated with a batch file:
  112.  
  113. MASM %1;
  114. LINK %1;
  115. EXE2BIN %1
  116. BASIC BIN2BLD
  117.  
  118. Name the batch file ASM2BLD.BAT and invoke it with:
  119.  
  120. A>ASM2BLD QPRINT
  121.  
  122.      The linker will display a "No STACK segment" warning, but this
  123. can be ignored for all COM, BIN, and BLOAD files.
  124.      One of the most confusing parts of Appendix C of the BASIC manual
  125. is the explanation of how to use the BLOAD command to load a program
  126. from memory.  The method involves interactive work with DEBUG,
  127. providing plenty of opportunities for error.  The process can be
  128. simplified.  The goal is to create a BLOAD module.  Appendix C does
  129. this by using the BSAVE command, but there are alternatives.
  130.      A BLOAD module is a binary image of the code to be executed,
  131. just like that produced by the EXE2BIN utility.  The only difference
  132. is that a 7-byte "header" is tacked onto the front of the file to
  133. identify it as a BLOAD module, supply a default load address, and
  134. indicate the length of the module.  This format is not documented in
  135. the BASIC literature, but it's easy to decipher with any file utility.
  136. The format is:
  137.  
  138. 1 byte -- FDH - BLOAD file ID
  139. 2 bytes -- default load address segment
  140. 2 bytes -- default load address offset
  141. 2 bytes -- length of file
  142.  
  143.      Thus, given a BIN file produced by EXE2BIN, all we need to do is
  144. discover the length, write out a header, and then write out the bytes
  145. of the file itself.  BIN2BLD.BAS is short and simple with no bells or
  146. whistles.  It automatically sets the default load address to B000:0000.
  147. This default is never to be used.  It was chosen so that if by accident
  148. you forget to specify a load address in the BLOAD command, your mistake
  149. will be immediately apparent.
  150.      There are several alternative methods of placing the BLOAD header
  151. at the start of a file.  One way is to load it with DEBUG, use the Move
  152. command to place it 7 bytes forward in memory, use the E command to
  153. create the header, use the R command to change CX to make the file
  154. length 7 bytes longer and then use the W command to write the file
  155. back out to disk.  A trickier method is to use DB and DW commands to
  156. place the 7 bytes of the header right in the assembly language listing.
  157. You must take care to realize that this will bias all addresses
  158. referred to in the listing by 7 bytes.  That only matters when you
  159. refer to messages and variable storage with the module -- CALLs and
  160. JMPs are self-relative, so they are not affected by being mixed around.
  161.      A machine language routine almost always needs one or more
  162. parameters.  In the case of QPRINT, two integers and a string variable
  163. are required.  BASIC never passes a value directly to a machine
  164. language subroutine.  Instead, it passes a "pointer," or indirect
  165. reference, telling the subroutine where to find the value.  In other
  166. words, it passes the VARPTR of each variable.  BASIC passes these
  167. VARPTR values by PUSHing them onto the stack before making an
  168. intersegment (far) CALL to the machine language code.
  169.      The best way to access these values is by setting the BP register
  170. to point to the top of the stack, and then reading the values therein
  171. by using the base-relative stack memory addressing mode -- accessing
  172. memory at offsets from BP.  Remember that the routine was reached via
  173. far CALL, so the parameters will be offset from the top of the stack
  174. by at least 4 bytes.  For instance, if a routine used only one
  175. parameter, an integer variable, the following sequence could be used
  176. to obtain the value of that variable:
  177.  
  178. 100 CALL MY_PROC(VAR%)
  179.       .
  180.       .
  181. mov bp,sp       ;point to top of the stack
  182. mov bx,[BP+4]   ;BX is the address of VAR%
  183. mov ax,[BX]     ;AX is value VAR%
  184.       .
  185.       .
  186. ret 2           ;DEBUG A command: use RETF 2
  187.  
  188.      The final instruction is a special form of the RET opcode which
  189. Intel created for exactly this application; i.e., clearing the stack
  190. of arguments passed to subroutines.  The number specified after the
  191. RET mnemonic is the number of bytes that must be discarded from the
  192. stack.  Since each parameter passed by BASIC is exactly 2 bytes long,
  193. the RET mnemonic should be followed by the number of arguments times
  194. 2.  Since QPRINT requires 3 arguments, it uses RET 6 to exit back to
  195. BASIC.
  196.      Be sure that you understand how to access a single integer
  197. argument, because the next part gets trickier.  First, the Intel
  198. standard is set up to accommodate reentrant procedures.  That's a
  199. procedure which might call itself, or be invoked "simultaneously" in
  200. a multi-tasking environment.  In that case, each invocation of the
  201. procedure must not affect or be affected by any other invocation.
  202. So each invocation is associated with a "frame" of data which must
  203. be kept separate from all others.
  204.      In order to keep them separate, the BP register "frame pointer"
  205. should be saved when the program begins executing.  Thus, the first
  206. opcode executed is a PUSH BP and the last opcode before the RET is a
  207. POP BP.  The PUSH BP adds just one complication -- all the arguments
  208. end up 2 bytes lower in the stack.  Keeping this in mind, we make a
  209. slight revision to the previous example:
  210.  
  211. push bp         ;save the frame pointer
  212. mov  bp,sp      ;point to top of the stack
  213. mov  bx,[BP+6]  ;BX is the address of VAR%
  214. mov  ax,[BX]    ;AX is value VAR%
  215.      .
  216.      .
  217. pop  bp         ;restore the frame pointer
  218. ret  2          ;DEBUG A command: use RETF 2
  219.  
  220. Notice that the address of VAR% is now found at [BP+6] instead of
  221. [BP+4].
  222.      The next step is learning to access more than one argument.  The
  223. best way to think of this is from the viewpoint of BASIC. It interprets
  224. each line of statements from left to right.  So in the line:
  225.  
  226. 100 CALL QPRINT(A$,ROW%,CLM%)
  227.  
  228. it sees the CALL statement and begins to prepare for the CALL.  It
  229. ascertains the value of the variable QPRINT which, in conjunction with
  230. the currently active DEF SEG, will be used as the address to call.
  231. Next, it encounters the variable A$.  It looks up the address of A$
  232. and pushes that value onto the stack.  Next, it pushes the address of
  233. ROW% and then it pushes the address of CLM%.  Finally, it makes a far
  234. CALL to the QPRINT routine.
  235.      The significance is this: since the address of A$ is pushed first,
  236. it will be farthest from the top of the stack.  The address of CLM%
  237. will be closest to the top of the stack because it was pushed last.
  238. This, after the machine language routine saves BP and copies SP into
  239. it, the address of CLM% will be at [BP+6].  It follows that the address
  240. of A$ will be even lower in the stack at [BP+10].  Thus, when you write
  241. your code, you calculate the offsets from BP by reading the CALL line
  242. from right to left, starting with an offset of 6 and adding 2 for each
  243. argument.
  244.      You might find it useful to set up some equates early in the
  245. program.  For instance, accessing the three variables of QPRINT could
  246. be simplified with:
  247.  
  248. clm_addr  equ [BP+6]    ;rightmost (6 + 0 * 2)
  249. row_addr  equ [BP+8]    ;center    (6 + 1 * 2)
  250. a$_addr   equ [BP+10]   ;leftmost  (6 + 2 * 2)
  251.       .
  252.       .
  253. mov   bx,clm_addr       ;get the address of CLM%
  254. mov   ax,[bx]           ;get the value of CLM%
  255.       .
  256.       .
  257.  
  258.      Then, if you change the number of positions of the arguments, you
  259. need only change the equates at the top of the listing.  Experienced
  260. programmers may choose to use the STRUC pseudo-op (MASM only) to set
  261. up the offsets.  This technique is especially valuable for Pascal
  262. programmers who need to pass complex data types to the machine language
  263. code.
  264.      Finally, there's one more complication.  When your routine
  265. processes numeric variables, BASIC passes the address where the value
  266. of the variable may be found.  But when you process string variables,
  267. BASIC adds another layer of indirection, passing the address of a
  268. string descriptor block.  The string descriptor block contains two
  269. items.  The first byte is the length of the string.  The following
  270. two bytes give the address of the first character of the string.  So,
  271. accessing the character of A$ is a four-step process:
  272.  
  273. mov bx,[bp+10]  ;BX has address of descriptor
  274. mov cl,[bx]     ;CL is the length
  275. mov si,[bx+1]   ;SI is address of first character
  276. mov al,[si]     ;AL is the first character
  277.  
  278.      Once you have SI pointing to the characters of the string and you
  279. have the length of the string in CL, you've got it licked.  In QPRINT,
  280. those characters are just copied from the BASIC work area into video
  281. memory.  Another program could compare the characters with those of
  282. other strings as part of a sorting process.  Or each character could
  283. be forced into uppercase just by modifying the byte at [SI] and working
  284. through the bytes for the length specified in CL.
  285.      In a more sophisticated application, the string could be scanned
  286. and interpreted as a command line to perform any of a number of
  287. special-purpose functions.  You could write a version of QPRINT which
  288. looks for special sequences which cause it to alter the color of the
  289. following bytes, or even the direction of cursor motion.  You could
  290. write your own version of the DRAW command that works with text-mode
  291. screens.
  292.  
  293. QPRINT.ASM:
  294.  
  295. ; QPRINT subroutine CALLed from BASIC.  This routine prints a BASIC
  296. ; string on the video display.  It works for color or monochrome in
  297. ; cards in 80-column text mode only.  Called from BASIC via:
  298. ;
  299. ;          CALL QPRINT(VAR$,ROW%,CLM%)
  300. ; Where:
  301. ;          CLM% is an integer variable name (value: 1-80)
  302. ;          ROW% is an integer variable name (value: 1-25)
  303. ;          VAR$ is a string variable name
  304. ;
  305. ; VAR$ is displayed beginning at position CLM% of line ROW%.  If it's
  306. ; too long, it will wrap around to the next line.
  307. ;
  308. ; Example use from BASIC:
  309. ;
  310. ; 10 CLEAR,60000!:QPRINT=60000!     ' use 3000 for 64K machines
  311. ; 20 BLOAD "qprint.bld",QPRINT      ' load at clear area in BASIC segment
  312. ; 30 FOR J=1 TO 255                 ' once for each ASCII character
  313. ; 40 CLM%=1:VAR$=STRING$(80,J)      ' 80-byte string of that character
  314. ; 50 FOR ROW%=1 TO 25               ' for each screen line
  315. ; 60 CALL QPRINT(VAR$,ROW%,CLM%)    ' display the 80 bytes
  316. ; 70 NEXT                           ' next line (fill screen)
  317. ; 80 NEXT                           ' next character code
  318. ;
  319.  
  320. code_seg segment
  321.          assume  CS:code_seg,DS:nothing,ES:nothing
  322.  
  323. qprint   proc    far
  324.      push     bp        ;save the frame pointer
  325.      mov     bp,sp        ;point to arguments on stack
  326.  
  327.      mov     bx,[bp+6]    ;get addr of CLM% storage
  328.      mov     di,[bx]    ;get the column value
  329.  
  330.      mov     bx,[bp+8]    ;get addr of ROW% storage
  331.      mov     dx,[bx]    ;get the screen line value into DL
  332.  
  333.      mov     bx,[bp+10]    ;get ptr to string descriptor
  334.      mov     ch,0        ;string length is 1 byte
  335.      mov     cl,[bx]    ;fetch the length
  336.      mov     si,[bx+1]    ;point SI to first character of VAR$
  337.  
  338.      cmp     cx,0        ;null string?
  339.      je     exit        ;if so, do nothing.  Else,
  340.  
  341. ; -- calculate the address in video memory from the ROW,CLM arguments
  342. ; -- using the formula: addr = (row-1)*160+(clm-1)*2
  343.  
  344.      dec     dx        ;adjust ROW from LOCATE format
  345.      mov     al,160
  346.      mul     dl        ;AX=(row-1)*160
  347.      dec     di        ;adjust column
  348.      shl     di,1        ;DI=(clm-1)*2
  349.      add     di,ax        ;DI has correct offset into video memory
  350.  
  351. ; -- find segment of the active display card
  352.  
  353.      mov     bx,0B800H    ;assume color/graphics card
  354.      int     11H        ;invoke EQUIPMENT-CHECK service
  355.      and     ax,30H
  356.      cmp     ax,30H        ;is it the B/W card?
  357.      jne     card_ok    ;no, go
  358.      mov     bx,0B000H    ;yes, set for monochrome
  359. card_ok:
  360.      mov     es,bx        ;point ES to video
  361.  
  362. ; -- DS:SI points to BASIC variables area
  363. ; -- ES:DI points to video card memory
  364. ; -- CX is the length of the string
  365. ; -- Now copy VAR$ to video memory, ignoring the display attribute
  366.  
  367. next:
  368.      movsb            ;DS:[SI] -> ES:[DI]
  369.                 ;SI=SI+1, DI=DI+1
  370.      inc     di        ;get past attribute byte
  371.      loop     next        ;do for entire length of VAR$
  372. exit:
  373.      push     ds
  374.      pop     es        ;restore BASIC segment regs
  375.  
  376.      pop     bp        ;restore frame pointer
  377.      ret     6        ;FAR return to BASIC, discarding 3 arguments
  378. qprint     endp
  379. code_seg ends
  380.      end     qprint        ;needed for .BIN file conversion
  381.  
  382.  
  383. BIN2BLD.BAS:
  384.  
  385. 5 'BIN2BLD.BAS: Program converts a BIN-format file into a BLOAD module
  386. 10 CLS:PRINT "--- Convert BIN file to BLOAD format ---":PRINT
  387. 20 INPUT "Filename: ",F$:IN.FILE$=F$+".BIN"
  388. 30 IN.FILE$=F$+".BIN":OUT.FILE$=F$+".BLD"
  389. 40 OPEN IN.FILE$ AS #1 LEN=1
  390. 50 OPEN OUT.FILE$ AS #2 LEN=1
  391. 60 FIELD #1,1 AS IN.B$:FIELD #2,1 AS OUT.B$
  392. 70 SIZE=LOF(1)                ' Size of BIN file
  393. 80 IF SIZE=0 THEN PRINT "Can't find input file.":CLOSE:GOTO 10
  394. 90 'Place the 7-byte header in the output file
  395. 100 LSET OUT.B$=CHR$(&HFD):PUT #2    ' BLOAD file ID byte
  396. 110 LSET OUT.B$=CHR$(0):PUT #2        ' Segment LSB
  397. 120 LSET OUT.B$=CHR$(&HB0):PUT #2    ' Segment MSB
  398. 130 LSET OUT.B$=CHR$(0):PUT #2        ' Offset LSB
  399. 140 LSET OUT.B$=CHR$(0):PUT #2        ' Offset MSB
  400. 150 LSET OUT.B$=CHR$(SIZE AND 255):PUT #2    ' Length LSB
  401. 160 LSET OUT.B$=CHR$(SIZE\256):PUT #2    ' Length MSB
  402. 190 'Copy the rest of the input file to the output file
  403. 200 FOR J=1 TO SIZE
  404. 210 GET #1:LSET OUT.B$=IN.B$:PUT #2
  405. 220 NEXT
  406. 230 CLOSE
  407. 240 PRINT:PRINT "File: ";OUT.FILE$;" is ";SIZE;" bytes long."
  408.  
  409. -----------------------------------------------------------------
  410.                Passing Filenames to Compiled BASIC
  411.          (BYTE Magazine November 1986 by Bruce Hubanks)
  412.  
  413.      When you start an application program, it is often desireable to
  414. be able to specify a filename on the DOS command line.  For instance,
  415. given a hypothetical data encryption program named Encode, you might
  416. like to be able to process a data file named Filex by typing:
  417.  
  418. ENCODE FILEX
  419.  
  420.      Many language compilers provide a library function to retrieve
  421. secondary filenames and parameters from the command line.  Microsoft's
  422. BASIC Compiler does not.
  423.      The assembly language subroutine called GETSPEC remedies this
  424. deficiency.  To make use of the routine from a BASIC application
  425. program, you simply include a couple of lines at the beginning of your
  426. program:
  427.  
  428. 10 '"FILENAME.EXT"
  429. 20 F$="            "
  430. 30 CALL GETSPEC(F$)
  431. 40 PRINT "Text remaining on command string is: ";F$
  432. 50 END
  433.  
  434. Then compile the program and link it to GETSPEC.  The result will be
  435. an executable application.  (If you use BASCOM's IO option, you won't
  436. even need BASRUN to run the program.)
  437.      Because GETSPEC obeys the Microsoft conventions for parameter
  438. passing, it could also be used to retrieve filenames for other language
  439. compilers that obey these conventions.  (GETSPEC cannot be used with
  440. Microsoft's BASIC Interpreter.)
  441.      A brief explanation of how DOS handles command-line information
  442. will help you understand GETSPEC.ASM.
  443.      When a program is invoked from the DOS command line, DOS creates
  444. a bookkeeping area called the program segment prefix (PSP) at the
  445. lowest available memory location.  Within this area, the data transfer
  446. area (DTA) contains all the characters typed after the program name.
  447.      On entry to the program, the stack pointer (SP) gives the code
  448. offset address of the calling program.  This address enables the
  449. subroutine to retrieve parameters from the calling program and pass
  450. values back to it.
  451.      GETSPEC starts by copying the all-important SP value into BP.
  452. [BP]+4 gives the code segment address.  The starting address of the
  453. PSP can be derived from this code segment address by subtracting 16.
  454. Adding 128 gives the start of the DTA, which contains a length byte
  455. followed by the specified number of bytes that were typed before the
  456. end of the line.
  457.      The GETSPEC subroutine uses the stack information to locate the
  458. command-string text in the DTA.  The program searches for the first
  459. nonblank character and then reads the text up to the first blank or
  460. end of text, whichever comes first.  The nonblank characters are
  461. copied into a string that was defined in the main BASIC program.
  462. The string storage location is at the address given by [BP]+6.
  463.      In the BASIC demonstration program above, F$ receives the filename
  464. from the subroutine.  The demonstration program allocates 12 bytes to
  465. F$ -- enough to contain a filename -- but you can allocate up to 255
  466. bytes to F$ if your application program needs to retrieve additional
  467. parameters from the DOS command line.
  468.      To incorporate GETSPEC into an existing program, use GETSPEC.ASM
  469. to generate an .OBJ file.  At the beginning of your application
  470. program, define a string constant to contain 12 blanks (you can use
  471. more if they are needed).  Use the statement:
  472.  
  473. CALL GETSPEC(F$)
  474.  
  475. to call the subroutine and get the necessary text into F$.
  476.      Compile your BASIC program to produce an .OBJ file.  Finally,
  477. link the two .OBJ files into a single .EXE file.
  478.      Here is a typical command sequence, given two source files named
  479. DEMO.BAS (above) and GETSPEC.ASM (below):
  480.  
  481. MASM GETSPEC
  482. BASCOM DEMO/O
  483. LINK DEMO+GETSPEC
  484.  
  485. The /O parameter tells BASCOM to create a single executable file
  486. called DEMO.EXE that contains all needed libraries.
  487.      Typing DEMO FILENAME.EXT activates the program, which should
  488. produce the message "Text remaining on command string is:
  489. FILENAME.EXT."
  490.  
  491. ; Routine to get a filename from the DOS command line
  492. ; using a call from a compiled BASIC program
  493. ;
  494. const segment word public 'const'
  495. const ends
  496. ;
  497. data  segment word public 'data'
  498. data  ends
  499. ;
  500. dgroup        group   data,const
  501. ;
  502. code  segment byte public 'code'
  503.       public  getspec
  504.       assume  cs:code,ds:dgroup
  505. ;
  506. getspec       proc    far
  507. ;
  508.       push    bp
  509.       mov     bp,sp
  510.       push    ax
  511.       push    si
  512.       push    di
  513.       push    dx
  514.       push    cx
  515.       push    bx
  516.       push    es
  517.       push    ds
  518. ;
  519.       mov     dx,[bp]+4
  520.       sub     dx,10h
  521.       mov     ds,dx
  522. ;
  523. ; Set up pointer to dta to get parameters
  524.       mov     si,0080h
  525.       mov     cl,[si]
  526.       mov     ch,0
  527.       inc     si
  528. ;
  529. ; Scan past the spaces
  530.       mov     al,' '
  531. getspec1:
  532.       cmp     [si],al
  533.       jne     getspec2
  534.       inc     si
  535.       loop    getspec1
  536.       jmp     getspec3
  537. ;
  538. ; move the rest into place
  539. getspec2:
  540.       mov     bx,[bp]+6
  541.       pop     ds
  542.       mov     di,[bx]+2
  543.       push    ds
  544.       mov     ds,dx
  545.       cld
  546.       rep     movsb
  547.       clc
  548.       jmp     getspecexit
  549. ;
  550. getspec3:
  551.       mov     ax,20
  552.       stc
  553.  
  554. getspecexit:
  555.       pop     ds
  556.       pop     es
  557.       pop     bx
  558.       pop     cx
  559.       pop     dx
  560.       pop     di
  561.       pop     si
  562.       pop     ax
  563.       pop     bp
  564.       ret     1*2
  565. ;
  566. getspec       endp
  567. code  ends
  568.       end
  569.  
  570. -----------------------------------------------------------------
  571.                       ROM Calls from BASIC
  572.             (BYTE Magazine November 1986 Best of BIX)
  573.  
  574.      ROM calls are easily coded in assembler.  Other languages have a
  575. general purpose call where the INT # is passed as well as the value of
  576. certain registers.  Following is a first pass at it in BASIC.  It does
  577. not currently return the new register values after the interrupt nor
  578. does it return the FLAG settings (used by some DOS calls).  However,
  579. it should suffice for many uses.
  580.      of the 3 "DEF FNxx" statements, the one for FNCC$ is used to
  581. assign to a string an assembler routine that is built on the fly using
  582. the INT value passed as well as various register values.  The other 2
  583. FNxx's merely break up FNCC$ into 2 pieces for easier handling.
  584.      The program below issues a video INT 16 (&H10) with AH set to 6
  585. for scroll up.  When AL=0 (as it is in this example) the entire window
  586. is blanked.  CX gives the upper left corner as 0,0 and DX gives the
  587. lower right corner as 10,40.  BH specifies the attribute used to fill
  588. in new lines.  More experienced programmers can craft a version like:
  589.  
  590. 100 CALL IR(IN,AX,BX,CX,DX,etc....)
  591.  
  592. Such a version could also return the register values after the
  593. interrupt as well as the flag settings.
  594.  
  595. 10 DEF FNAA$(DS,BX,CX,DX,BP,SI,DI)=MKI$(&H5655)+MKI$(&H61E)+CHR$(&HBB)+MKI$(BX)+CHR$(&HB9)+MKI$(CX)+CHR$(&HBA)+MKI$(DX)+CHR$(&HBD)+MKI$(BP)+CHR$(&HBE)+MKI$(SI)+CHR$(&HBF)+MKI$(DI)+CHR$(&HBB)+MKI$(DS)
  596. 20 DEF FNBB$(AX,ES,IN)=MKI$(&HD88E)+CHR$(&HB8)+MKI$(ES)+MKI$(&HC08E)+CHR$(&HB8)+MKI$(AX)+CHR$(&HCD)+LEFT$(MKI$(IN),1)+MKI$(&H1F07)+MKI$(&H5D5E)+CHR$(&HCB)
  597. 30 DEF FNCC$(IN,AX,BX,CX,DX,BP,SI,DI,DS,ES)=FNAA$(DS,BX,CX,DX,BP,SI,DI)+FNBB$(AX,ES,IN)
  598. 40 GOTO 60
  599. 50 I=VARPTR(S$):J=CVI(CHR$(PEEK(I+1))+CHR$(PEEK(I+2))):CALL J:RETURN
  600.  
  601. REM AH=6,AL=0 to clear all,BH=attr, Dh=line 10, DL=column
  602. REM 40:clear window
  603. REM Video INT 16 (Hex 10)
  604.  
  605. 60 S$=FNCC$(16,&H600,&H4E00,0,&HA28,0,0,0,0,0):GOSUB 50
  606. 70 END
  607.  
  608. To issue Shift-PrtSc from BASIC code:
  609.  
  610. 60 S$=FNCC$(5,0,0,0,0,0,0,0,0,0):GOSUB 50
  611.  
  612.